﻿using System.Collections.Generic;
using UnityEngine.Pool;

namespace System {

    public interface IReadOnlyByteFusionData : IReadOnlyFusionData<byte>, IReadOnlyByteData { }
    public interface IValueChangedByteFusionData : IReadOnlyByteFusionData, IValueChangedFusionData<byte>, IValueChangedByteData { }
    public interface IByteFusionDataNotifier : IValueChangedByteFusionData, IFusionDataNotifier<byte>, IByteDataNotifier { }
    public interface IByteFusionData : IByteFusionDataNotifier, IFusionData<byte> { }

    public interface IReadOnlySByteFusionData : IReadOnlyFusionData<sbyte>, IReadOnlySByteData { }
    public interface IValueChangedSByteFusionData : IReadOnlySByteFusionData, IValueChangedFusionData<sbyte>, IValueChangedSByteData { }
    public interface ISByteFusionDataNotifier : IValueChangedSByteFusionData, IFusionDataNotifier<sbyte>, ISByteDataNotifier { }
    public interface ISByteFusionData : ISByteFusionDataNotifier, IFusionData<sbyte> { }

    public interface IReadOnlyShortFusionData : IReadOnlyFusionData<short>, IReadOnlyShortData { }
    public interface IValueChangedShortFusionData : IReadOnlyShortFusionData, IValueChangedFusionData<short>, IValueChangedShortData { }
    public interface IShortFusionDataNotifier : IValueChangedShortFusionData, IFusionDataNotifier<short>, IShortDataNotifier { }
    public interface IShortFusionData : IShortFusionDataNotifier, IFusionData<short> { }

    public interface IReadOnlyUShortFusionData : IReadOnlyFusionData<ushort>, IReadOnlyUShortData { }
    public interface IValueChangedUShortFusionData : IReadOnlyUShortFusionData, IValueChangedFusionData<ushort>, IValueChangedUShortData { }
    public interface IUShortFusionDataNotifier : IValueChangedUShortFusionData, IFusionDataNotifier<ushort>, IUShortDataNotifier { }
    public interface IUShortFusionData : IUShortFusionDataNotifier, IFusionData<ushort> { }

    public interface IReadOnlyIntFusionData : IReadOnlyFusionData<int>, IReadOnlyIntData { }
    public interface IValueChangedIntFusionData : IReadOnlyIntFusionData, IValueChangedFusionData<int>, IValueChangedIntData { }
    public interface IIntFusionDataNotifier : IValueChangedIntFusionData, IFusionDataNotifier<int>, IIntDataNotifier { }
    public interface IIntFusionData : IIntFusionDataNotifier, IFusionData<int> { }

    public interface IReadOnlyUIntFusionData : IReadOnlyFusionData<uint>, IReadOnlyUIntData { }
    public interface IValueChangedUIntFusionData : IReadOnlyUIntFusionData, IValueChangedFusionData<uint>, IValueChangedUIntData { }
    public interface IUIntFusionDataNotifier : IValueChangedUIntFusionData, IFusionDataNotifier<uint>, IUIntDataNotifier { }
    public interface IUIntFusionData : IUIntFusionDataNotifier, IFusionData<uint> { }

    public interface IReadOnlyLongFusionData : IReadOnlyFusionData<long>, IReadOnlyLongData { }
    public interface IValueChangedLongFusionData : IReadOnlyLongFusionData, IValueChangedFusionData<long>, IValueChangedLongData { }
    public interface ILongFusionDataNotifier : IValueChangedLongFusionData, IFusionDataNotifier<long>, ILongDataNotifier { }
    public interface ILongFusionData : ILongFusionDataNotifier, IFusionData<long> { }

    public interface IReadOnlyULongFusionData : IReadOnlyFusionData<ulong>, IReadOnlyULongData { }
    public interface IValueChangedULongFusionData : IReadOnlyULongFusionData, IValueChangedFusionData<ulong>, IValueChangedULongData { }
    public interface IULongFusionDataNotifier : IValueChangedULongFusionData, IFusionDataNotifier<ulong>, IULongDataNotifier { }
    public interface IULongFusionData : IULongFusionDataNotifier, IFusionData<ulong> { }

    public interface IReadOnlyFloatFusionData : IReadOnlyFusionData<float>, IReadOnlyFloatData { }
    public interface IValueChangedFloatFusionData : IReadOnlyFloatFusionData, IValueChangedFusionData<float>, IValueChangedFloatData { }
    public interface IFloatFusionDataNotifier : IValueChangedFloatFusionData, IFusionDataNotifier<float>, IFloatDataNotifier { }
    public interface IFloatFusionData : IFloatFusionDataNotifier, IFusionData<float> { }

    public interface IReadOnlyDoubleFusionData : IReadOnlyFusionData<double>, IReadOnlyDoubleData { }
    public interface IValueChangedDoubleFusionData : IReadOnlyDoubleFusionData, IValueChangedFusionData<double>, IValueChangedDoubleData { }
    public interface IDoubleFusionDataNotifier : IValueChangedDoubleFusionData, IFusionDataNotifier<double>, IDoubleDataNotifier { }
    public interface IDoubleFusionData : IDoubleFusionDataNotifier, IFusionData<double> { }

    public interface IReadOnlyFusionData<T> : IReadOnlyData<T> {

        new IFusionData<T> AsReadWrite();
        IData<T> IReadOnlyData<T>.AsReadWrite() => throw new NotSupportedException(
            $"{nameof(IReadOnlyData<T>)}.{nameof(IReadOnlyData<T>.AsReadWrite)} " +
            $"已被 {nameof(IReadOnlyFusionData<T>)}.{nameof(AsReadWrite)} 覆盖"
        );

        int Count { get; }

        IReadOnlyCollection<IReadOnlyData<T>> Items { get; }
    }

    public interface IValueChangedFusionData<T> : IReadOnlyFusionData<T>, IValueChangedData<T> {
    }

    public interface IFusionDataNotifier<T> : IValueChangedFusionData<T>, IDataNotifier<T> {
    }

    public interface IFusionData<T> : IFusionDataNotifier<T> {

        bool IsDirty { get; }

        void Add(IReadOnlyData<T> item);
        void AddWithOutEvent(IReadOnlyData<T> item);

        bool Remove(IReadOnlyData<T> item);
        bool RemoveWithOutEvent(IReadOnlyData<T> item);

        void Clear();
        void ClearWithOutEvent();

        void SetMaxValue(T value);

        void SetMinValue(T value);
    }

    [Implement(typeof(IReadOnlyByteFusionData))]
    [Implement(typeof(IValueChangedByteFusionData))]
    [Implement(typeof(IByteFusionDataNotifier))]
    [Implement(typeof(IByteFusionData))]
    [Implement(typeof(IReadOnlyFusionData<byte>))]
    [Implement(typeof(IValueChangedFusionData<byte>))]
    [Implement(typeof(IFusionDataNotifier<byte>))]
    [Implement(typeof(IFusionData<byte>))]
    internal sealed class KHBD_ByteFusionData : KHBD_FusionData<byte>, IByteFusionData {

        protected override byte GetDefaultValue() => default;

        protected sealed override byte Calculate(byte current, byte next) => (byte)(current + next);

        protected override byte GetDefaultMaxValue() => byte.MaxValue;

        protected override byte GetDefaultMinValue() => byte.MinValue;

        protected override byte Clamp(byte value, byte min, byte max) => Math.Clamp(value, min, max);
    }

    [Implement(typeof(IReadOnlySByteFusionData))]
    [Implement(typeof(IValueChangedSByteFusionData))]
    [Implement(typeof(ISByteFusionDataNotifier))]
    [Implement(typeof(ISByteFusionData))]
    [Implement(typeof(IReadOnlyFusionData<sbyte>))]
    [Implement(typeof(IValueChangedFusionData<sbyte>))]
    [Implement(typeof(IFusionDataNotifier<sbyte>))]
    [Implement(typeof(IFusionData<sbyte>))]
    internal sealed class KHBD_SByteFusionData : KHBD_FusionData<sbyte>, ISByteFusionData {

        protected override sbyte GetDefaultValue() => default;

        protected sealed override sbyte Calculate(sbyte current, sbyte next) => (sbyte)(current + next);

        protected override sbyte GetDefaultMaxValue() => sbyte.MaxValue;

        protected override sbyte GetDefaultMinValue() => sbyte.MinValue;

        protected override sbyte Clamp(sbyte value, sbyte min, sbyte max) => Math.Clamp(value, min, max);
    }

    [Implement(typeof(IReadOnlyShortFusionData))]
    [Implement(typeof(IValueChangedShortFusionData))]
    [Implement(typeof(IShortFusionDataNotifier))]
    [Implement(typeof(IShortFusionData))]
    [Implement(typeof(IReadOnlyFusionData<short>))]
    [Implement(typeof(IValueChangedFusionData<short>))]
    [Implement(typeof(IFusionDataNotifier<short>))]
    [Implement(typeof(IFusionData<short>))]
    internal sealed class KHBD_ShortFusionData : KHBD_FusionData<short>, IShortFusionData {

        protected override short GetDefaultValue() => default;

        protected sealed override short Calculate(short current, short next) => (short)(current + next);

        protected override short GetDefaultMaxValue() => short.MaxValue;

        protected override short GetDefaultMinValue() => short.MinValue;

        protected override short Clamp(short value, short min, short max) => Math.Clamp(value, min, max);
    }

    [Implement(typeof(IReadOnlyUShortFusionData))]
    [Implement(typeof(IValueChangedUShortFusionData))]
    [Implement(typeof(IUShortFusionDataNotifier))]
    [Implement(typeof(IUShortFusionData))]
    [Implement(typeof(IReadOnlyFusionData<ushort>))]
    [Implement(typeof(IValueChangedFusionData<ushort>))]
    [Implement(typeof(IFusionDataNotifier<ushort>))]
    [Implement(typeof(IFusionData<ushort>))]
    internal sealed class KHBD_UShortFusionData : KHBD_FusionData<ushort>, IUShortFusionData {

        protected override ushort GetDefaultValue() => default;

        protected sealed override ushort Calculate(ushort current, ushort next) => (ushort)(current + next);

        protected override ushort GetDefaultMaxValue() => ushort.MaxValue;

        protected override ushort GetDefaultMinValue() => ushort.MinValue;

        protected override ushort Clamp(ushort value, ushort min, ushort max) => Math.Clamp(value, min, max);
    }

    [Implement(typeof(IReadOnlyIntFusionData))]
    [Implement(typeof(IValueChangedIntFusionData))]
    [Implement(typeof(IIntFusionDataNotifier))]
    [Implement(typeof(IIntFusionData))]
    [Implement(typeof(IReadOnlyFusionData<int>))]
    [Implement(typeof(IValueChangedFusionData<int>))]
    [Implement(typeof(IFusionDataNotifier<int>))]
    [Implement(typeof(IFusionData<int>))]
    internal sealed class KHBD_IntFusionData : KHBD_FusionData<int>, IIntFusionData {

        protected override int GetDefaultValue() => default;

        protected sealed override int Calculate(int current, int next) => current + next;

        protected override int GetDefaultMaxValue() => int.MaxValue;

        protected override int GetDefaultMinValue() => int.MinValue;

        protected override int Clamp(int value, int min, int max) => Math.Clamp(value, min, max);
    }

    [Implement(typeof(IReadOnlyUIntFusionData))]
    [Implement(typeof(IValueChangedUIntFusionData))]
    [Implement(typeof(IUIntFusionDataNotifier))]
    [Implement(typeof(IUIntFusionData))]
    [Implement(typeof(IReadOnlyFusionData<uint>))]
    [Implement(typeof(IValueChangedFusionData<uint>))]
    [Implement(typeof(IFusionDataNotifier<uint>))]
    [Implement(typeof(IFusionData<uint>))]
    internal sealed class KHBD_UintFusionData : KHBD_FusionData<uint>, IUIntFusionData {

        protected override uint GetDefaultValue() => default;

        protected sealed override uint Calculate(uint current, uint next) => current + next;

        protected override uint GetDefaultMaxValue() => uint.MaxValue;

        protected override uint GetDefaultMinValue() => uint.MinValue;

        protected override uint Clamp(uint value, uint min, uint max) => Math.Clamp(value, min, max);
    }

    [Implement(typeof(IReadOnlyLongFusionData))]
    [Implement(typeof(IValueChangedLongFusionData))]
    [Implement(typeof(ILongFusionDataNotifier))]
    [Implement(typeof(ILongFusionData))]
    [Implement(typeof(IReadOnlyFusionData<long>))]
    [Implement(typeof(IValueChangedFusionData<long>))]
    [Implement(typeof(IFusionDataNotifier<long>))]
    [Implement(typeof(IFusionData<long>))]
    internal sealed class KHBD_LongFusionData : KHBD_FusionData<long>, ILongFusionData {

        protected override long GetDefaultValue() => default;

        protected sealed override long Calculate(long current, long next) => current + next;

        protected override long GetDefaultMaxValue() => long.MaxValue;

        protected override long GetDefaultMinValue() => long.MinValue;

        protected override long Clamp(long value, long min, long max) => Math.Clamp(value, min, max);
    }

    [Implement(typeof(IReadOnlyULongFusionData))]
    [Implement(typeof(IValueChangedULongFusionData))]
    [Implement(typeof(IULongFusionDataNotifier))]
    [Implement(typeof(IULongFusionData))]
    [Implement(typeof(IReadOnlyFusionData<ulong>))]
    [Implement(typeof(IValueChangedFusionData<ulong>))]
    [Implement(typeof(IFusionDataNotifier<ulong>))]
    [Implement(typeof(IFusionData<ulong>))]
    internal sealed class KHBD_ULongFusionData : KHBD_FusionData<ulong>, IULongFusionData {

        protected override ulong GetDefaultValue() => default;

        protected sealed override ulong Calculate(ulong current, ulong next) => current + next;

        protected override ulong GetDefaultMaxValue() => ulong.MaxValue;

        protected override ulong GetDefaultMinValue() => ulong.MinValue;

        protected override ulong Clamp(ulong value, ulong min, ulong max) => Math.Clamp(value, min, max);
    }

    [Implement(typeof(IReadOnlyFloatFusionData))]
    [Implement(typeof(IValueChangedFloatFusionData))]
    [Implement(typeof(IFloatFusionDataNotifier))]
    [Implement(typeof(IFloatFusionData))]
    [Implement(typeof(IReadOnlyFusionData<float>))]
    [Implement(typeof(IValueChangedFusionData<float>))]
    [Implement(typeof(IFusionDataNotifier<float>))]
    [Implement(typeof(IFusionData<float>))]
    internal sealed class KHBD_FloatFusionData : KHBD_FusionData<float>, IFloatFusionData {

        protected override float GetDefaultValue() => default;

        protected sealed override float Calculate(float current, float next) => current + next;

        protected override float GetDefaultMaxValue() => float.MaxValue;

        protected override float GetDefaultMinValue() => float.MinValue;

        protected override float Clamp(float value, float min, float max) => Math.Clamp(value, min, max);
    }

    [Implement(typeof(IReadOnlyDoubleFusionData))]
    [Implement(typeof(IValueChangedDoubleFusionData))]
    [Implement(typeof(IDoubleFusionDataNotifier))]
    [Implement(typeof(IDoubleFusionData))]
    [Implement(typeof(IReadOnlyFusionData<double>))]
    [Implement(typeof(IValueChangedFusionData<double>))]
    [Implement(typeof(IFusionDataNotifier<double>))]
    [Implement(typeof(IFusionData<double>))]
    internal sealed class KHBD_DoubleFusionData : KHBD_FusionData<double>, IDoubleFusionData {

        protected override double GetDefaultValue() => default;

        protected sealed override double Calculate(double current, double next) => current + next;

        protected override double GetDefaultMaxValue() => double.MaxValue;

        protected override double GetDefaultMinValue() => double.MinValue;

        protected override double Clamp(double value, double min, double max) => Math.Clamp(value, min, max);
    }

    public abstract class KHBD_FusionData<T> : KHBD_PoolObject, IFusionData<T> {

        private static readonly ObjectPool<HashSet<IReadOnlyData<T>>> m_SetPool = new(
            createFunc: () => new(8),
            actionOnRelease: item => item.Clear(),
            maxSize: 1024 * 1024,
            collectionCheck: false
        );


        public IFusionData<T> AsReadWrite() {
            AssertDispose();
            return this;
        }

        private event IChangable.ChangedDelegate InnerChanged;
        public event IChangable.ChangedDelegate Changed {
            add {
                AssertDispose();
                InnerChanged += value;
            }
            remove {
                AssertDispose();
                InnerChanged -= value;
            }
        }

        private event IValueChangedData<T>.ValueChangedDelegate InnerValueChanged;
        public event IValueChangedData<T>.ValueChangedDelegate ValueChanged {
            add {
                AssertDispose();
                m_CachedValue = Value;
                InnerValueChanged += value;
            }
            remove {
                AssertDispose();
                m_CachedValue = Value;
                InnerValueChanged -= value;
            }
        }

#if UNITY_EDITOR
        private Guid m_GUID;
#endif

        protected override void Allocate() {
#if UNITY_EDITOR
            m_GUID = Guid.NewGuid();
#endif
            MaxValue = GetDefaultMaxValue();
            MinValue = GetDefaultMinValue();
            m_CachedValue = GetDefaultValue();
            IsDirty = true;
        }

        protected override void Release() {
            if (InnerChanged != null) {
                InnerChanged = null;
            }
            if (InnerValueChanged != null) {
                InnerValueChanged = null;
            }
            DoClear();
            IsDirty = false;
        }

        private HashSet<IReadOnlyData<T>> m_Set;

        public bool IsDirty { get; private set; }

        private T m_CachedValue;

        public int Count => m_Set == null ? 0 : m_Set.Count;

        public IReadOnlyCollection<IReadOnlyData<T>> Items {
            get {
                AssertDispose();
                return m_Set;
            }
        }

        public void Add(IReadOnlyData<T> item) {
            DoAdd(item);
            Dirty();
        }
        public void AddWithOutEvent(IReadOnlyData<T> item) {
            DoAdd(item);
            IsDirty = true;
        }
        private void DoAdd(IReadOnlyData<T> item) {
            AssertDispose();
            UnityEngine.Assertions.Assert.IsNotNull(item);
            if (m_Set == null) {
                m_Set = m_SetPool.Get();
            } else {
                if (m_Set.Contains(item)) {
                    UnityEngine.Assertions.Assert.IsFalse(m_Set.Contains(item));
                }
            }
            m_Set.Add(item);
            item.Changed += Data_Changed;
        }

        private void Data_Changed() {
            Dirty();
        }

        public bool Remove(IReadOnlyData<T> item) {
            if (DoRemove(item)) {
                Dirty();
                return true;
            }
            return false;
        }
        public bool RemoveWithOutEvent(IReadOnlyData<T> item) {
            if (DoRemove(item)) {
                IsDirty = true;
                return true;
            }
            return false;
        }
        private bool DoRemove(IReadOnlyData<T> item) {
            AssertDispose();
            UnityEngine.Assertions.Assert.IsNotNull(item);
            if (m_Set == null) {
                return false;
            }
            var result = m_Set.Remove(item);
            if (result) {
                item.Changed -= Data_Changed;
                if (m_Set.Count == 0) {
                    m_SetPool.Release(m_Set);
                    m_Set = null;
                }
                return true;
            }
            return false;
        }

        private bool DoClear() {
            if (m_Set != null) {
                var isDirty = false;
                if (m_Set.Count > 0) {
                    isDirty = true;
                    foreach (var item in m_Set) {
                        if (item != null && !item.IsDisposed) {
                            item.Changed -= Data_Changed;
                        }
                    }
                }
                m_SetPool.Release(m_Set);
                m_Set = null;
                return isDirty;
            }
            return false;
        }

        public virtual void Clear() {
            AssertDispose();
            if (DoClear()) {
                Dirty();
            }
        }
        public virtual void ClearWithOutEvent() {
            AssertDispose();
            if (DoClear()) {
                IsDirty = true;
            }
        }

        protected T MaxValue { get; private set; }
        protected abstract T GetDefaultMaxValue();
        public void SetMaxValue(T value) {
            AssertDispose();
            MaxValue = value;
            Dirty();
        }

        protected T MinValue { get; private set; }
        protected abstract T GetDefaultMinValue();
        public void SetMinValue(T value) {
            AssertDispose();
            MinValue = value;
            Dirty();
        }

        public virtual T Value {
            get {
                AssertDispose();
                if (IsDirty) {
                    m_CachedValue = GetDefaultValue();
                    if (m_Set != null) {
                        foreach (var item in m_Set) {
                            m_CachedValue = Calculate(m_CachedValue, item.Value);
                        }
                    }
                    m_CachedValue = Clamp(m_CachedValue, MinValue, MaxValue);
                    IsDirty = false;
                }
                return m_CachedValue;
            }
        }

        protected abstract T GetDefaultValue();

        protected abstract T Clamp(T value, T min, T max);

        protected void ValueCached() => IsDirty = false;

        protected abstract T Calculate(T current, T next);

        private void Dirty() {
            IsDirty = true;
            Notify();
        }

        private void Notify() {
            if (KHBD.Context.IsApplicationQuit) {
                return;
            }
            InnerValueChanged?.Invoke(m_CachedValue, Value);
            InnerChanged?.Invoke();
        }

        void IDataNotifier<T>.NotifyChangedEvent() {
            AssertDispose();
            if (KHBD.Context.IsApplicationQuit) {
                return;
            }
            InnerChanged?.Invoke();
        }

        void IDataNotifier<T>.NotifyValueChangedEvent() {
            AssertDispose();
            Notify();
        }

        public override string ToString() => Value.ToString();
    }
}